module LightweightControls.Anime

open System
open System.Threading

// We use Windows Forms timers to avoid cross thread illegal access
open System.Windows.Forms


type AnimeTask(d: int, a: seq<int>) = 
  member x.Due = d
  member x.Action = a.GetEnumerator()

type AnimeHeap() as x =    
  let data = new ResizeArray<AnimeTask>()
  
  let left idx = 2 * (idx + 1) - 1
  let right idx = 2* (idx + 1)
  let parent idx = ((idx + 1) / 2) - 1
  
  let swap a b =
    let tmp = data.[a]
    data.[a] <- data.[b]
    data.[b] <- tmp
  
  let PercolateUp p =
    let mutable par = parent p
    let mutable pos = p
    while (pos > 0) && (data.[pos].Due < data.[par].Due) do
      swap pos par
      pos <- par
      par <- parent pos

  let rec PercolateDown p =
    let l = left p
    let r = right p
    let pos = data.Count
    if l < pos then
      if r < pos then
        if data.[l].Due > data.[r].Due then
          swap p r
          PercolateDown r
        else
          swap pos l
          PercolateDown l
      elif p <> pos - 1 then
        swap p (pos - 1)
        PercolateUp p
  
  member x.Count = data.Count
  
  member x.Insert el =
    let pos = data.Count
    data.Add(el)    
    PercolateUp pos

  member x.Top = if data.Count > 0 then Some data.[0] else None
  
  member x.Remove () =
    if data.Count <> 0 then
      let r = data.[0]
      PercolateDown 0
      data.RemoveAt(data.Count - 1)
      Some r
    else
      None

  member x.Remove (el) =
    let idx = data.IndexOf(el)
    if idx <> -1 then
      PercolateDown idx
      data.RemoveAt(data.Count - 1)
      true
    else
      false

let internal timer = new Timer()

// Global timer handling

let internal start = DateTime.Now
let internal heap = new AnimeHeap()

let internal time() = DateTime.Now - start

let internal setAlertMillis what interval =
  // FIXME: this may be a problem
  let exp = (int (interval + time()).TotalMilliseconds)

  heap.Insert(new AnimeTask(exp, what))
  if timer.Enabled && heap.Top.Value.Due = exp then
    timer.Enabled <- false
  
  if not timer.Enabled then
    let tosleep = heap.Top.Value.Due - (int (time().TotalMilliseconds))
    timer.Interval <- if tosleep <= 0 then 1 else tosleep

let internal removeTask t =
  heap.Remove t

let internal setAlertDate what (datetime:DateTime) =
  let t = datetime - DateTime.Now
  setAlertMillis what t

timer.Tick.Add(fun _ ->
  let currt = (int (time().TotalMilliseconds)) + 10
  while heap.Count > 0 && currt >= heap.Top.Value.Due do
    let el = heap.Remove()
    el.Value.Action.MoveNext() |> ignore
    
  if heap.Count > 0 then
    timer.Interval <- heap.Top.Value.Due - (int (time().TotalMilliseconds))
  else
    timer.Enabled <- false
)

(*
function AnimeTimerTick() {
  var t = AnimeGlobalTimer;
  var currt = t.Time() + 10;
  while (t.Heap.Count() > 0 && currt >= t.Heap.Top().Due) {
    var el = t.Heap.Remove();
    el.Task.DoAction(currt - el.Due);
  }
  if (t.Heap.Count() > 0) {
    currt = t.Time();
    AnimeTimerRunning = setTimeout('AnimeTimerTick()', t.Heap.Top().Due - currt);
  } else {
    AnimeTimerRunning = null;
  }
}
*)